- 下面是门主引擎为你系统梳理 ESP32-S3 定时器的学习框架(基于ESP-IDF)。
- 注意晶振质量:如果你的 DevKit 没有乐鑫(Espressif)标识的开发板晶振可能偏差较大(~100 ppm),长时间计时会有明显误差,这是产品、盗版硬件问题,与定时器类型无关
1. SDK路径:ESP-IDF 定时器需要掌握的核心知识点
1.1 ESP-IDF有哪些定时器?
ESP-IDF 中的定时器分为两大类:
定时器
├── 系统定时器
│ ├── esp_timer(高分辨率软件定时器)
│ └── FreeRTOS Timer(RTOS 软件定时器)
└── 通用定时器
└── GPTimer(独立硬件定时器)2. 整体流程:各定时器的运行机制如何?
2.1 esp_timer 运行机制
硬件系统定时器计数
↓ 超时
ISR 通知 esp_timer 任务
↓
高优先级 esp_timer 任务串行执行回调函数2.2 GPTimer 运行机制(定时器中断读取 AHT30 温湿度数值的方案)
硬件定时器组外设计数(完全由硬件完成)
↓ 计数值达到警报值
触发中断(ISR 回调)
↓
ISR 中发送消息到队列(xQueueSendFromISR)
↓
普通任务从队列读取消息(xQueueReceive)
↓
执行 I2C 读取 AHT30、esp_log 打印关键点:GPTimer硬件计数完全独立,中断处理的延迟不影响定时精度。(General Purpose)
3. 对比区别:如何依据场景需求选择定时器?
3.1 图表对比
| 对比项 | GPTimer(硬件定时器) | esp_timer(软件定时器) | FreeRTOS Timer |
|---|---|---|---|
| 时钟源 | XTAL_CLK / APB_CLK | XTAL_CLK | FreeRTOS Tick |
| 分辨率 | 纳秒级(可配置) | 1 µs | ~1–10 ms(Tick 周期) |
| 回调上下文 | ISR 中直接执行 | 高优先级任务(默认) | 低优先级定时器任务 |
| 硬件资源 | 独享,不受其他模块干扰 | 系统共用,阻塞时受影响 | 纯软件,无硬件资源 |
| 额外功能 | 事件捕获、ETM 联动 | 睡眠时间自动补偿 | 无 |
| 使用难度 | 较复杂,精度最高 | 简单 | 最简单 |
3.2 如何选择定时器?
| 定时器 | 分辨率 | 适用场景 |
|---|---|---|
| esp_timer | 1 µs | 软件定时,管理多个定时器,使用简单 |
| GPTimer | 纳秒级可配置 | 高精度、波形生成、事件捕获 |
| FreeRTOS Timer | 受 tick 频率限制(通常 1~10 ms) | 简单延时,可移植性好 |
4. 定时器中断的两种分发方式怎么理解?
4.1 定时器选择推荐:
针对多传感器场景,推荐使用 esp_timer:
- 定时需求是"每隔一小时触发一次",精度要求不高,
esp_timer的 1 µs 分辨率完全足够。 esp_timer支持周期性定时器,可以自动重启,无需手动管理。- 它能处理多个定时器实例,适合同时管理 AHT30、BMP280 等多个传感器的采集周期。
- 支持从 Light-sleep 唤醒后自动补偿睡眠时间,保持时间准确。
4.1 回调分发方式选择
esp_timer 提供两种回调分发方式:
- 任务分发法(默认):从高优先级 esp_timer 任务中分发回调,适合你的场景(每小时触发一次,对时序要求不高)。在回调中使用 FreeRTOS 队列/信号量通知对应的传感器采集任务即可。(推荐使用)
- 中断分发法(ISR):延迟更低,适合运行时间仅几微秒的简单回调。(这个方法要用 idf.py menuconfig,或者是打开 SDK configuration 在里面配置相关的参数)

5. ESP32-S3 模组及模组时钟源分析
5.1 为什么深睡眠模式需要结合外部无源晶振来补偿时间?
- ESP32-S3 核心开发板上面的 ESP32-S3 模组里面,已经集成了一个 40MHz 的外部高速晶振。这个晶振是非常稳定的,但是模组并没有集成外部低速的 32.768kHz 无源晶振(即类似石英表使用的时钟源)。
- 目前该模组集成的是 RC 振荡器。RC 振荡器的原理是利用电阻电容的充放电过程,受温度影响较大,因此在几个小时之内可能会产生几秒的偏移误差。如果长期使用,这种累积性的误差会非常显著。
- 因此,在以下场景中需要结合外部无源晶振作为补偿:
- ESP32-S3 需要进入深度睡眠(Deep Sleep)。
- 需要结合定时器中断,周期性地采集传感器数值。
- 补偿原理:
- 高速晶振(40MHz)是与内部数字内核及 CPU 关联的。当 CPU 等数字内核进入深度睡眠后,高速晶振将停止工作,无法累积计算当前时间。
- 那么在深度睡眠结束并退出时,就需要使用外部低速晶振源(或者内部低速 RC 振荡器)来补偿和计算具体睡眠了多久。为了保证精度,建议使用外部低速无源晶振。因为外部的低速无源晶振在深度睡眠的时候,依旧是工作的。
5.2 ESP32-S3 GPTimer 硬件定时器外设
5.2.1 为什么是 GPTimer?
GPTimer 是基于 ESP32-S3 的定时器组(Timer Group)外设实现的,它具有真正的硬件计数器: 所有通用定时器均基于 16 位预分频器和 54 位可自动重新加载向上/向下计数器。 这和 STM32 的定时器结构非常相似:
- 预分频器(Prescaler):16 位,分频系数 2~65536
- 计数器(Counter):54 位,可配置向上或向下计数
- 自动重装(Auto-reload):硬件层面自动重装计数值
- 报警/比较值(Alarm/Compare):计数器到达设定值时触发中断
5.2.2 esp_timer 和 GPTimer 的本质区别
| GPTimer | esp_timer | |
|---|---|---|
| 本质 | 硬件计数器,直接操作寄存器 | 软件定时器,基于系统定时器实现 |
| 类比 STM32 | ✅ 类似 STM32 的 TIMx 定时器 | ❌ 更像 STM32 的 SysTick 软件定时器 |
| 计数器可读 | ✅ 可通过 gptimer_get_raw_count() 读取实时计数值 | ❌ 没有直接暴露计数器 |
| 分辨率 | 纳秒级可配置 | 固定 1 µs |
6. 如何使用esp_timer软件定时器实现定时器中断?
6.1 示例一:纯 esp_timer 模拟 LED 闪烁
6.1.1 纯 esp_timer 模拟 LED 闪烁具体步骤说明
| 步骤 | 做什么 | 实现的功能 |
|---|---|---|
| 1 | 初始化 GPIO 38 为输出模式 | 让引脚可以控制 LED |
| 2 | 创建"亮灯定时器"(led_on_timer) | 负责 5 秒后点亮 LED |
| 3 | 创建"熄灯定时器"(led_off_timer) | 负责 5 秒后熄灯并调用亮灯LED |
| 4 | 启动触发定时器(一次性,5 秒后触发) | 开始计时 |
| 5 | 亮灯定时器到期 → 回调点亮 LED,启动熄灭定时器 | LED 亮起,同时开始 5 秒倒计时 |
| 6 | 熄灭定时器到期 → 回调熄灭 LED,重新启动亮灯定时器 | LED 熄灭,等待下一个 5 秒 |
6.1.2 纯 esp_timer 模拟 LED 闪烁代码实现
C
#include "esp_timer.h" // esp_timer 相关 API
#include "esp_log.h" // ESP_LOGI 日志打印
#include "driver/gpio.h" // GPIO 控制
#define LED_GPIO GPIO_NUM_38 // LED 连接的引脚号
#define BLINK_INTERVAL_US (5 * 1000000) // 5 秒,单位:微秒
#define LED_ON_DURATION_US (1 * 1000000) // 亮 1 秒,单位:微秒
static const char *TAG = "ESP_TIMER_LED";
// -------------------------------------------------------
// 定时器句柄:相当于定时器的"身份证",后续操作都靠它
// -------------------------------------------------------
static esp_timer_handle_t led_on_timer_handle = NULL; // 触发闪烁的定时器
static esp_timer_handle_t led_off_timer_handle = NULL; // 熄灭 LED 的定时器
// -------------------------------------------------------
// 回调函数 A:点亮 LED,然后启动 1 秒后熄灭的定时器
// 这个函数在"触发定时器"到期时自动被调用
// -------------------------------------------------------
static void led_on_callback(void *arg)
{
gpio_set_level(LED_GPIO, 1); // 点亮 LED(输出高电平)
ESP_LOGI(TAG, "LED ON");
// 启动熄灭定时器,1 秒后熄灭 LED
ESP_ERROR_CHECK(esp_timer_start_once(led_off_timer_handle, LED_ON_DURATION_US));
}
// -------------------------------------------------------
// 回调函数 B:熄灭 LED,然后重新启动 5 秒触发定时器
// 这个函数在"熄灭定时器"到期时自动被调用
// -------------------------------------------------------
static void led_off_callback(void *arg)
{
gpio_set_level(LED_GPIO, 0); // 熄灭 LED(输出低电平)
ESP_LOGI(TAG, "LED OFF");
// 重新启动触发定时器,等待下一个 5 秒
// esp_timer_start_once:一次性定时器,到期后只触发一次
ESP_ERROR_CHECK(esp_timer_start_once(led_on_timer_handle, BLINK_INTERVAL_US));
}
void app_main(void)
{
// -------------------------------------------------------
// 第一步:初始化 GPIO 38 为输出模式
// -------------------------------------------------------
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << LED_GPIO), // 选择引脚 38
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE, // 不使用 GPIO 中断
};
gpio_config(&io_conf);
gpio_set_level(LED_GPIO, 0); // 初始状态:LED 熄灭
// -------------------------------------------------------
// 第二步:创建"点亮 LED"定时器
// -------------------------------------------------------
const esp_timer_create_args_t led_on_timer_args = {
.callback = &led_on_callback, // 到期时调用 led_on_callback
.name = "led_on_timer",
};
ESP_ERROR_CHECK(esp_timer_create(&led_on_timer_args, &led_on_timer_handle));
// -------------------------------------------------------
// 第三步:创建"熄灭 LED"定时器
// esp_timer_create_args_t:定时器配置参数结构体
// .callback:定时器到期时调用哪个函数
// .name:定时器名字(调试用,随便起)
// -------------------------------------------------------
const esp_timer_create_args_t led_off_timer_args = {
.callback = &led_off_callback, // 到期时调用 led_off_callback
.name = "led_off_timer",
};
// esp_timer_create:根据配置参数创建定时器,把"身份证"存入 led_off_timer_handle
ESP_ERROR_CHECK(esp_timer_create(&led_off_timer_args, &led_off_timer_handle));
// -------------------------------------------------------
// 第四步:启动触发定时器,5 秒后第一次触发
// esp_timer_start_once:一次性定时器,到期后自动停止
// -------------------------------------------------------
ESP_ERROR_CHECK(esp_timer_start_once(led_on_timer_handle, BLINK_INTERVAL_US));
ESP_LOGI(TAG, "Start LED timer");
// -------------------------------------------------------
// app_main 直接返回,主任务自动删除
// 定时器会继续在后台运行,不受影响
// -------------------------------------------------------
}6.2 示例二:Task + 信号量 + esp_timer(推荐用于实际项目)
6.2.1 Task + 信号量 + esp_timer实现的具体步骤说明
| 步骤 | 做什么 | 实现的功能 |
|---|---|---|
| 1 | 初始化 GPIO 38 | 让引脚可以控制 LED |
| 2 | 创建二值信号量 | 定时器和 Task 之间的"通知桥梁" |
| 3 | 创建"亮灯定时器" | 负责 1 秒后点亮 LED |
| 4 | 创建"周期性闪烁定时器" | 每 5 秒自动触发一次,释放信号量 |
| 5 | 创建 LED 控制 Task | 专门等待信号量,收到后操作 LED |
| 6 | 定时器回调释放信号量 → Task 收到信号量 → 点亮 LED → 启动熄灭定时器 | 完整的闪烁流程 |
6.2.2
C
#include "esp_timer.h" // esp_timer 相关 API
#include "esp_log.h" // 日志打印
#include "driver/gpio.h" // GPIO 控制
#include "freertos/FreeRTOS.h" // FreeRTOS 基础
#include "freertos/task.h" // xTaskCreate 等任务 API
#include "freertos/semphr.h" // 信号量 API
// -------------------------------------------------------
// 宏定义:引脚号和时间间隔
// -------------------------------------------------------
#define LED_GPIO GPIO_NUM_38
#define INTERVAL_US (5 * 1000000) // 每 5 秒触发一次亮灯,单位:微秒
#define LED_ON_DURATION_US (1 * 1000000) // 亮灯持续 1 秒,单位:微秒
static const char *TAG = "LED_TIMER_TASK";
// -------------------------------------------------------
// 全局变量
// -------------------------------------------------------
// 二值信号量:只有"有信号(1)"和"无信号(0)"两种状态
// 定时器回调通过它通知 Task:"该亮灯了"
static SemaphoreHandle_t led_sem_handle = NULL;
// 亮灯触发定时器的句柄:每 5 秒触发一次,通知 Task 亮灯
static esp_timer_handle_t led_on_timer_handle = NULL;
// 熄灯定时器的句柄:亮灯 1 秒后触发,将 LED 熄灭
static esp_timer_handle_t led_off_timer_handle = NULL;
// -------------------------------------------------------
// 回调函数 A:熄灯
// 当"熄灯定时器"到期(亮灯 1 秒后),自动调用此函数
// 执行位置:esp_timer 内部任务(非中断上下文)
// -------------------------------------------------------
static void led_off_callback(void *arg)
{
gpio_set_level(LED_GPIO, 0); // 将 GPIO 38 输出低电平 → LED 熄灭
ESP_LOGI(TAG, "LED 熄灭");
}
// -------------------------------------------------------
// 回调函数 B:释放信号量,通知 Task 执行亮灯操作
// 当"亮灯触发定时器"每 5 秒到期时,自动调用此函数
// 注意:此处只释放信号量,不直接操作 GPIO
// 实际亮灯操作在 Task 中完成,符合 ESP-IDF 最佳实践
// -------------------------------------------------------
static void led_sem_callback(void *arg)
{
// xSemaphoreGiveFromISR:在回调/中断上下文中释放信号量
// 第二个参数 NULL:不需要请求任务切换
xSemaphoreGiveFromISR(led_sem_handle, NULL);
}
// -------------------------------------------------------
// LED 控制 Task:专门负责等待信号量并执行亮灯操作
// 收到信号量 → 点亮 LED → 启动熄灯定时器(1 秒后熄灭)
// 使用 for(;;) 无限循环,符合 FreeRTOS Task 的标准写法
// -------------------------------------------------------
static void led_control_task(void *pvParameters)
{
for (;;) {
// xSemaphoreTake:等待信号量
// portMAX_DELAY:永久等待,直到信号量到来为止
// 在等待期间,此 Task 处于阻塞状态,不占用 CPU
if (xSemaphoreTake(led_sem_handle, portMAX_DELAY) == pdTRUE) {
// 收到信号量,说明 5 秒定时器已到期,执行亮灯
gpio_set_level(LED_GPIO, 1); // 将 GPIO 38 输出高电平 → LED 点亮
ESP_LOGI(TAG, "LED 点亮");
// 启动熄灯定时器:1 秒后自动调用 led_off_callback 熄灭 LED
// esp_timer_start_once:一次性定时器,到期后只触发一次,不自动重复
// 注意:若上一次熄灯定时器还未到期,需先调用 esp_timer_stop() 再启动
esp_timer_stop(led_off_timer_handle); // 确保熄灯定时器未在运行
ESP_ERROR_CHECK(esp_timer_start_once(led_off_timer_handle, LED_ON_DURATION_US));
}
}
}
// -------------------------------------------------------
// app_main:程序入口,完成所有初始化后直接返回
// 返回后主任务自动删除,其他 Task 和定时器继续运行
// -------------------------------------------------------
void app_main(void)
{
// -------------------------------------------------------
// 第一步:初始化 GPIO 38 为输出模式
// gpio_config_t:GPIO 配置结构体
// -------------------------------------------------------
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << LED_GPIO), // 选择引脚 38(位掩码形式)
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.pull_up_en = GPIO_PULLUP_DISABLE, // 不启用内部上拉
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE, // 不使用 GPIO 中断
};
gpio_config(&io_conf);
gpio_set_level(LED_GPIO, 0); // 初始状态:LED 熄灭
// -------------------------------------------------------
// 第二步:创建二值信号量
// 二值信号量只有两种状态:有信号(1)和无信号(0)
// 定时器回调"给出"信号量,Task"取走"信号量
// -------------------------------------------------------
led_sem_handle = xSemaphoreCreateBinary();
// -------------------------------------------------------
// 第三步:创建"sem 定时器"(周期性)
// -------------------------------------------------------
const esp_timer_create_args_t led_on_timer_args = {
.callback = &led_sem_callback, // 到期时释放信号量
.name = "led_sem_timer",
};
ESP_ERROR_CHECK(esp_timer_create(&led_on_timer_args, &led_on_timer_handle));
// -------------------------------------------------------
// 第四步:创建"熄灯定时器"
// esp_timer_create_args_t:定时器配置参数
// .callback:定时器到期时调用哪个函数
// .name:定时器名字(仅用于调试,随便起)
// esp_timer_create:根据配置创建定时器,句柄存入 led_off_timer
// -------------------------------------------------------
const esp_timer_create_args_t led_off_timer_args = {
.callback = &led_off_callback, // 到期时调用熄灯回调
.name = "led_off_timer",
};
ESP_ERROR_CHECK(esp_timer_create(&led_off_timer_args, &led_off_timer_handle));
// esp_timer_start_periodic:启动周期性定时器
// 每隔 INTERVAL_US(5 秒)自动触发一次,无需手动重启
ESP_ERROR_CHECK(esp_timer_start_periodic(led_on_timer_handle, INTERVAL_US));
// -------------------------------------------------------
// 第五步:创建 LED 控制 Task
// xTaskCreate 参数说明:
// led_control_task:Task 函数
// "led_ctrl_task":Task 名字(调试用)
// 2048:栈大小(字节)
// NULL:传给 Task 的参数(不需要)
// 5:Task 优先级(数字越大优先级越高)
// NULL:Task 句柄(不需要保存)
// -------------------------------------------------------
xTaskCreate(led_control_task, "led_ctrl_task", 2048, NULL, 5, NULL);
// -------------------------------------------------------
// app_main 直接返回
// 主任务自动删除,led_control_task 和两个定时器继续运行
// -------------------------------------------------------
}6.3 GPTimer(通用硬件定时器)
GPTimer(通用硬件定时器)也是定时器,而且是硬件定时器,比
esp_timer更底层。它直接操作 ESP32-S3 定时器组外设的硬件计数器,具有更高的精度和更多的功能(如事件捕获、与 GPIO 联动等)。GPTimer 概述
6.3.1 GPTimer 的使用流程
1. gptimer_new_timer() —— 创建定时器,获得句柄
↓
2. gptimer_set_alarm_action() —— 配置警报值(到多少计数触发中断)
↓
3. gptimer_register_event_callbacks() —— 注册警报回调函数
↓
4. gptimer_enable() —— 使能定时器(申请系统资源)
↓
5. gptimer_start() —— 启动定时器,开始计数
↓
6. 计数器到达警报值 → 触发回调函数6.3.2 GPTimer完整代码
C
#include "driver/gpio.h"
#include "driver/gptimer.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// -------------------------------------------------------
// 宏定义
// -------------------------------------------------------
#define LED_GPIO GPIO_NUM_38
#define TIMER_RESOLUTION_HZ (1 * 1000 * 1000) // 1 MHz,1 次计数 = 1 微秒
// 各阶段的计数值(单位:微秒)
#define WAIT_5S_COUNT (5 * 1000 * 1000) // 等待 5 秒 = 5,000,000 计数
#define LED_ON_COUNT (1 * 1000 * 1000) // 亮灯 1 秒 = 1,000,000 计数
// 熄灯后等待 1 秒(题目要求熄灭 1 秒后再等 5 秒,此处熄灭即进入下一轮等待)
static const char *TAG = "GPTIMER_LED";
// -------------------------------------------------------
// 状态枚举:描述当前 LED 处于哪个阶段
// -------------------------------------------------------
typedef enum {
STATE_WAITING, // 等待 5 秒阶段(LED 熄灭,等待下次点亮)
STATE_LED_ON, // 亮灯 1 秒阶段
} led_state_t;
// 当前状态,初始为"等待 5 秒"
static volatile led_state_t current_state = STATE_WAITING;
// GPTimer 句柄
static gptimer_handle_t gptimer = NULL;
// -------------------------------------------------------
// GPTimer 警报回调函数
// 在警报事件发生时自动调用,运行在中断上下文中
// 注意:不能在此处调用阻塞式 API
// -------------------------------------------------------
static bool timer_alarm_callback(gptimer_handle_t timer,
const gptimer_alarm_event_data_t *edata,
void *user_ctx)
{
gptimer_alarm_config_t new_alarm = {};
if (current_state == STATE_WAITING) {
// 当前处于"等待 5 秒"阶段,5 秒到了 → 点亮 LED,切换到亮灯阶段
gpio_set_level(LED_GPIO, 1);
ESP_EARLY_LOGI(TAG, "LED 点亮");
current_state = STATE_LED_ON;
// 设置下一次警报:再过 1 秒触发(亮灯 1 秒后熄灭)
// alarm_count = 当前警报值 + 1 秒的计数
new_alarm.alarm_count = edata->alarm_value + LED_ON_COUNT;
new_alarm.flags.auto_reload_on_alarm = false;
gptimer_set_alarm_action(timer, &new_alarm);
} else if (current_state == STATE_LED_ON) {
// 当前处于"亮灯 1 秒"阶段,1 秒到了 → 熄灭 LED,切换到等待阶段
gpio_set_level(LED_GPIO, 0);
ESP_EARLY_LOGI(TAG, "LED 熄灭");
current_state = STATE_WAITING;
// 设置下一次警报:再过 5 秒触发(等待 5 秒后再次点亮)
new_alarm.alarm_count = edata->alarm_value + WAIT_5S_COUNT;
new_alarm.flags.auto_reload_on_alarm = false;
gptimer_set_alarm_action(timer, &new_alarm);
}
// 返回 false:不需要请求任务切换
return false;
}
void app_main(void)
{
// -------------------------------------------------------
// 第一步:初始化 GPIO 38 为输出模式
// -------------------------------------------------------
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << LED_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(LED_GPIO, 0); // 初始状态:LED 熄灭
// -------------------------------------------------------
// 第二步:创建 GPTimer
// gptimer_config_t:定时器配置结构体
// .clk_src:时钟源,选默认(通常为 APB 或 XTAL)
// .direction:计数方向,向上计数
// .resolution_hz:分辨率,1 MHz = 每计数 1 次代表 1 微秒
// -------------------------------------------------------
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = TIMER_RESOLUTION_HZ,
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
// -------------------------------------------------------
// 第三步:注册警报回调函数
// 当计数器到达警报值时,自动调用 timer_alarm_callback
// -------------------------------------------------------
gptimer_event_callbacks_t cbs = {
.on_alarm = timer_alarm_callback,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
// -------------------------------------------------------
// 第四步:配置第一次警报
// 第一次:等待 5 秒后点亮 LED
// alarm_count = 5,000,000(5 秒后触发)
// auto_reload_on_alarm = false:不自动重载,由回调手动更新下一次警报值
// -------------------------------------------------------
gptimer_alarm_config_t alarm_config = {
.alarm_count = WAIT_5S_COUNT,
.flags.auto_reload_on_alarm = false,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
// -------------------------------------------------------
// 第五步:使能并启动定时器
// gptimer_enable():使能定时器,申请系统资源(电源管理锁等)
// gptimer_start():启动计数器,开始计数
// -------------------------------------------------------
ESP_ERROR_CHECK(gptimer_enable(gptimer));
ESP_ERROR_CHECK(gptimer_start(gptimer));
// -------------------------------------------------------
// app_main 直接返回,主任务自动删除
// GPTimer 继续在后台运行,不受影响
// -------------------------------------------------------
}